// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * rn6752 ahd chip driver code for LomboTech
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 */

#include <linux/init.h>
#include <linux/module.h>

#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/viss_isp.h>

#include <media/lombo_viss.h>

#include <mach/debug.h>
#include "../ccc.h"

static const cc_reg_t rn6752_dvp_ahd_720_480[] = {
	/*
	 * if clock source(Xin) of RN675x is 26MHz,
	 * please add these procedures marked first
	 * { 0xD2, 0x85 }, disable auto clock detect
	 * { 0xD6, 0x37 },  27MHz default
	 * { 0xD8, 0x18 },  switch to 26MHz clock
	 * mdelay(100 },  delay 100ms
	 */
	/* { 0x80, 0x34 }, */
	/* { RN6752_DVP_REG_DELAY_FLAG, 20}, */
	{ 0x81, 0x01 },
	{ 0xA3, 0x00 },
	{ 0xDB, 0x8F },
	{ 0xFF, 0x00 },
	{ 0x2C, 0x30 },
	{ 0x50, 0x00 },
	{ 0x56, 0x00 },
	{ 0x63, 0x09 },
	{ 0x59, 0x00 },
	{ 0x5A, 0x00 },
	{ 0x58, 0x01 },
	{ 0x07, 0x23 },
	{ 0x2F, 0x14 },
	{ 0x5E, 0x03 },
	{ 0x5B, 0x00 },

	{ 0x3A, 0x04 },
	{ 0x3E, 0x32 },
	{ 0x40, 0x04 },
	{ 0x46, 0x23 },
	{ 0x28, 0x92 },
	{ 0x00, 0x00 },
	{ 0x2D, 0xF2 },
	{ 0x0D, 0x20 },
	{ 0x05, 0x00 },
	{ 0x04, 0x80 },
	{ 0x11, 0x03 },
	{ 0x37, 0x33 },
	{ 0x61, 0x6C },
	{ 0xDF, 0xFF },
	{ 0x8E, 0x00 },
	{ 0x8F, 0x00 },
	{ 0x8D, 0x31 },
	{ 0x89, 0x00 },
	{ 0x88, 0xC1 },
	{ 0x81, 0x01 },
	{ 0x96, 0x00 },
	{ 0x97, 0x0B },
	{ 0x98, 0x00 },
	{ 0x9A, 0x40 },
	{ 0x9B, 0xE1 },
	{ 0x9C, 0x00 },
};

static const cc_reg_t rn6752_dvp_ahd_720p[] = {
	/*
	 * if clock source(Xin) of RN675x is 26MHz,
	 * please add these procedures marked first
	 * { 0xD2, 0x85 }, disable auto clock detect
	 * { 0xD6, 0x37 },  27MHz default
	 * { 0xD8, 0x18 },  switch to 26MHz clock
	 * mdelay(100 },  delay 100ms
	 */
	/* { 0x80, 0x34 }, */
	/* { RN6752_DVP_REG_DELAY_FLAG, 20}, */
	{ 0x81, 0x01 }, /* turn on video decoder */
	{ 0xA3, 0x04 }, /* enable 72MHz sampling */
	{ 0xDB, 0x8F },  /* internal use  */
	{ 0xFF, 0x00 },  /* switch to ch0 (default; optional) */
	{ 0x2C, 0x30 },  /* select sync slice points */
	{ 0x50, 0x02 },  /* 720p resolution select for BT.601 */
	{ 0x56, 0x00 }, /* disable SAV & EAV for BT601; */
	/* 0x00 enable SAV & EAV for BT656 */
	{ 0x63, 0xBD },  /* filter control */
	{ 0x59, 0x00 },  /* extended register access */
	{ 0x5A, 0x02 },  /* data for extended register */
	{ 0x58, 0x01 },  /* enable extended register write */
	{ 0x07, 0x23 },  /* 720p format */
	{ 0x2F, 0x04 },  /* internal use */
	{ 0x5E, 0x0B },  /* enable H-scaling control */
	{ 0x51, 0x44 },  /* scale factor1 */
	{ 0x52, 0x86 },  /* scale factor2 */
	{ 0x53, 0x22 },  /* scale factor3 */
	{ 0x3A, 0x04 },  /* no channel information insertion; */
	/* invert VBLK for frame valid */
	{ 0x3E, 0x32 },  /* AVID & VBLK out for BT.601 */
	{ 0x40, 0x04 },  /* no channel information insertion; */
	/* invert VBLK for frame valid */
	{ 0x46, 0x23 },  /* AVID & VBLK out for BT.601 */
	{ 0x28, 0x92 },  /* cropping */
	{ 0x00, 0x20 },  /* internal use  */
	{ 0x2D, 0xF2 },  /* cagc adjust */
	{ 0x0D, 0x20 },  /* cagc initial value */
	{ 0x05, 0x00 },  /* sharpness */
	/* { 0x02, 0x80 }, */  /* Contrast */
	/* { 0x03, 0x80 }, */  /* Saturation */
	/* { 0x04, 0x80 }, */  /* hue */
	{ 0x37, 0x33 },
	{ 0x61, 0x6C },
	{ 0x1A, 0x83 },
	{ 0x01, 0x46 },  /* Brightness */
	{ 0xDF, 0xFE },  /* enable 720p format */
	{ 0x8E, 0x00 },  /* single channel output for VP */
	{ 0x8F, 0x80 }, /* 720p mode for VP */
	{ 0x8D, 0x31 },  /* enable VP out */
	{ 0x89, 0x09 },  /* select 72MHz for SCLK */
	{ 0x88, 0xC1 },  /* enable SCLK out */
	{ 0x81, 0x01 },  /* turn on video decoder */
	{ 0x96, 0x00 },  /* select AVID & VBLK as status indicator */
	{ 0x97, 0x0B },  /* enable status indicator out on AVID,VBLK & VSYNC */
	{ 0x98, 0x00 },  /* video timing pin status */
	{ 0x9A, 0x40 },  /* select AVID & VBLK as status indicator */
	{ 0x9B, 0xE1 },  /* enable status indicator out on HSYNC */
	{ 0x9C, 0x00 },  /* video timing pin status */
};

static struct cc_power_act power_up_act[] = {
	{CC_RESET, CC_DOWN},
	{CC_DELAY, 100},
	{CC_RESET,  CC_UP},
	{CC_DELAY, 100},
};

static struct cc_power_act power_down_act[] = {
	{CC_RESET, CC_DOWN},
	{CC_DELAY, 100},
};

#define RN6752_WIDTH_720_480		720
#define RN6752_HEIGHT_720_480		480
#define RN6752_WIDTH_720p		1280
#define RN6752_HEIGHT_720p		720

static const struct cc_frame_cfg frame_cfgs[] = {
	{
		/* N9V1_FPGA */
		.width = RN6752_WIDTH_720_480,
		.height = RN6752_HEIGHT_720_480,
		.framerate = { 1, 25 },	/* 25 fps */
		.reg_cfgs[0].regs = rn6752_dvp_ahd_720_480,
		.reg_cfgs[0].regs_cnt = ARRAY_SIZE(rn6752_dvp_ahd_720_480),
		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
		.colorspace = V4L2_COLORSPACE_JPEG,
		.field = V4L2_FIELD_NONE,
	},
	{
		.width = RN6752_WIDTH_720p,
		.height = RN6752_HEIGHT_720p,
		.framerate = { 1, 25 },	/* 25 fps */
		.reg_cfgs[0].regs = rn6752_dvp_ahd_720p,
		.reg_cfgs[0].regs_cnt = ARRAY_SIZE(rn6752_dvp_ahd_720p),
		.mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
		.colorspace = V4L2_COLORSPACE_JPEG,
		.field = V4L2_FIELD_NONE,
	},
};

static int sensor_detect(struct cc_sensor_info *cc_si)
{
	u8 tmp[2];
	int ret = 0;
	u16 id;

	/* read deviceID Low byte,should be 0x01 */
	ret = cc_si->read_reg(cc_si, 0xFD, &tmp[0]);
	if (ret != 0) {
		PRT_ERR("tmp[0]=%x\n", tmp[0]);
		return -ENODEV;
	}

	/* read deviceID High byte,should be 0x04 */
	ret = cc_si->read_reg(cc_si, 0xFE, &tmp[1]);
	if (ret != 0) {
		PRT_ERR("tmp[0]=%x,tmp[1]=%x\n", tmp[0], tmp[1]);
		return -ENODEV;
	}

	id = (tmp[1] << 8) | tmp[0];
	PRT_INFO("id: 0x%04x.\n", id);

	if (id != 0x0401) {
		PRT_ERR("ID wrong! (0x%04x != 0x2000)\n", id);
		return -ENODEV;
	}

	return 0;
}

static int sensor_init(struct cc_sensor_info *cc_si)
{
	struct timespec ts_s;
	struct timespec ts_f;
	int ret = 0;

	PRT_INFO("\n");

	ret = sensor_detect(cc_si);
	if (ret != 0)
		return ret;

	ktime_get_ts(&ts_s);

	ktime_get_ts(&ts_f);

	PRT_INFO("write %s rn6752_dvp_ahd_720p list finish.\n", cc_si->name);
	PRT_INFO("timeused %d %d\n", (int)(ts_f.tv_sec - ts_s.tv_sec),
		(int)(ts_f.tv_nsec - ts_s.tv_nsec));


	return 0;
}

static int sensor_set_stream(struct cc_sensor_info *cc_si, int on)
{
	int ret = 0;

	if (on) {
		ret = cc_si->write_reg(cc_si, 0x08, 0x30);
		if (ret)
			PRT_ERR("%d\n", on);
	} else {
		ret = cc_si->write_reg(cc_si, 0x08, 0x34);
		if (ret)
			PRT_ERR("%d\n", on);
	}
	return ret;
}

/*
 * ctrl for raw sensor finish
 */


#define DRIVER_NAME "rn6752_ahd"

static int sensor_probe(struct platform_device *pdev)
{
	struct cc_sensor_info *cc_si = NULL;
	int ret = 0;

	PRT_INFO("*********probe begin\n");

	cc_si = devm_kzalloc(&pdev->dev, sizeof(*cc_si), GFP_KERNEL);
	if (cc_si == NULL) {
		PRT_ERR("Can't alloc memory!\n");
		return -ENOMEM;
	}

	cc_si->reg_bits = 8;
	cc_si->val_bits = 8;

	cc_si->name = DRIVER_NAME;

	cc_si->power_up_act = power_up_act;
	cc_si->power_down_act = power_down_act;
	cc_si->npwr_up_acts = ARRAY_SIZE(power_up_act);
	cc_si->npwr_dn_acts = ARRAY_SIZE(power_down_act);

	cc_si->framecfgs = frame_cfgs;
	cc_si->nfrmcfgs = ARRAY_SIZE(frame_cfgs);

	ret = cc_sensor_probe(pdev, cc_si);
	if (ret < 0)
		return ret;

	cc_si->detect = sensor_detect;
	cc_si->sensor_init = sensor_init;
	cc_si->set_stream = sensor_set_stream;
	cc_si->sensor_ioctl = NULL;

	PRT_INFO("probe end\n");
	return 0;
}

static int sensor_remove(struct platform_device *pdev)
{
	struct cc_sensor_info *cc_si = platform_get_drvdata(pdev);

	cc_sensor_remove(pdev);

	devm_kfree(&pdev->dev, cc_si);

	return 0;
}

static const struct of_device_id rn6752_ahd_of_match[] = {
	{
		.compatible = DRIVER_NAME,
	},
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rn6752_ahd_of_match);

static struct platform_driver rn6752_ahd_driver = {
	.driver = {
		.of_match_table	= rn6752_ahd_of_match,
		.name		= DRIVER_NAME,
		.owner		= THIS_MODULE,
	},
	.probe = sensor_probe,
	.remove = sensor_remove,
};

module_platform_driver(rn6752_ahd_driver);

MODULE_DESCRIPTION("RN6752 AHD chip driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);
